/**
 * 文件上传公共工具函数
 * 适配uni-app的文件上传组件使用
 * 
 * 功能特性：
 * - 支持多种存储类型（MinIO、阿里云OSS、腾讯云COS等）
 * - 支持直传和后端转发两种上传模式
 * - 支持分片上传大文件
 * - 兼容H5、微信小程序、APP等多端环境
 * - 完善的错误处理和进度回调
 * 
 * @author 若依开发团队
 * @version 2.0.0
 */

import request from '@/utils/request'
import config from '@/config'
import { getToken } from '@/utils/auth'
import { getFileConfigs } from '@/store/modules/fileConfig'
import { getFileUrl } from '@/api/filemanager/upload'
import {
    abortMultipartUpload,
    completeMultipartUpload,
    getBatchPresignedUploadUrls,
    initMultipartUpload
} from '@/api/filemanager/multipart'

// ================================
// 常量定义
// ================================

/** 文件ID计数器 */
let fileIdCounter = 0

/** 文件上传配置项 - 可根据需要调整这些参数 */
export const UPLOAD_CONFIG = {
    // 分片上传阈值（字节）- 超过此大小自动使用分片上传
    CHUNK_THRESHOLD: 20 * 1024 * 1024, // 20MB
    
    // 分片大小（字节）- 每个分片的大小
    CHUNK_SIZE: 5 * 1024 * 1024, // 5MB
    
    // 并发上传数 - 同时上传的分片数量
    CONCURRENCY: 3,
    
    // 重试次数 - 分片上传失败时的重试次数
    MAX_RETRIES: 3,
    
    // 重试延迟配置（毫秒）
    RETRY_DELAY: {
        BASE: 1000,    // 基础延迟1秒
        MAX: 5000,     // 最大延迟5秒
        MULTIPLIER: 2  // 指数退避倍数
    },
    
    // 超时配置（毫秒）
    TIMEOUT: {
        CHUNK_UPLOAD: 60000,  // 分片上传超时60秒
        NORMAL_UPLOAD: 300000 // 普通上传超时5分钟
    },
    
    // 错误率阈值 - 超过此比例停止上传
    ERROR_RATE_THRESHOLD: 0.5 // 50%
}

// ================================
// 工具函数
// ================================

/**
 * 生成唯一文件ID
 * @param {string} prefix - ID前缀
 * @returns {string} 唯一文件ID
 */
export function generateFileId(prefix = 'file') {
    return `${prefix}_${Date.now()}_${++fileIdCounter}`
}

/**
 * 格式化文件大小
 * @param {number} bytes - 字节数
 * @returns {string} 格式化后的文件大小
 */
export function formatFileSize(bytes) {
    if (bytes === 0) return '0 B'
    const k = 1024
    const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
    const i = Math.floor(Math.log(bytes) / Math.log(k))
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}

/**
 * 复制文件路径到剪贴板
 * @param {string} path - 文件路径
 */
export function copyPath(path) {
    uni.setClipboardData({
        data: path,
        success: () => {
            uni.showToast({
                title: '路径已复制到剪贴板',
                icon: 'success'
            })
        },
        fail: () => {
            uni.showToast({
                title: '复制失败',
                icon: 'error'
            })
        }
    })
}

/**
 * 生成图片预览URL
 * @param {Object} file - 文件对象
 * @returns {Promise<string|null>} 预览URL
 */
export function generatePreviewUrl(file) {
    return new Promise((resolve) => {
        if (file.type && file.type.startsWith('image/')) {
            resolve(file.path || file.tempFilePath || null)
        } else {
            resolve(null)
        }
    })
}

/**
 * 获取文件名（兼容不同平台）
 * @param {Object} fileObj - 文件对象
 * @returns {string} 文件名
 */
function getFileName(fileObj) {
    return fileObj.name || 
           fileObj.file?.name || 
           fileObj.file?.path || 
           fileObj.file?.tempFilePath || 
           '未知文件'
}

/**
 * 创建带认证的请求头
 * @param {Object} additionalHeaders - 额外的请求头
 * @returns {Object} 请求头对象
 */
function createAuthHeaders(additionalHeaders = {}) {
    const headers = {
        'repeatSubmit': 'false',
        ...additionalHeaders
    }
    
    const token = getToken()
    if (token) {
        headers['Authorization'] = 'Bearer ' + token
    }
    
    return headers
}

/**
 * 解析MinIO XML错误信息
 * @param {string} xmlData - XML错误数据
 * @returns {string} 错误信息
 */
function parseMinIOError(xmlData) {
    if (!xmlData || !xmlData.includes('<Error>')) {
        return '未知错误'
    }
    
    const codeMatch = xmlData.match(/<Code>(.*?)<\/Code>/)
    const messageMatch = xmlData.match(/<Message>(.*?)<\/Message>/)
    
    if (codeMatch && messageMatch) {
        return `MinIO错误: ${codeMatch[1]} - ${messageMatch[1]}`
    }
    
    return '解析错误信息失败'
}

// ================================
// 验证函数
// ================================

/**
 * 检查文件类型
 * @param {Object} file - 文件对象
 * @param {Array<string>} allowedTypes - 允许的文件类型
 * @returns {boolean} 是否通过验证
 */
export function checkFileType(file, allowedTypes) {
    if (!allowedTypes || allowedTypes.length === 0) {
        return true
    }
    
    // 兼容不同平台的文件名获取方式
    let fileName = file.name || file.path || file.tempFilePath || ''
    
    if (!fileName || typeof fileName !== 'string') {
        return false
    }
    
    const lowerFileName = fileName.toLowerCase()
    const fileExt = lowerFileName.substring(lowerFileName.lastIndexOf('.') + 1)
    return allowedTypes.includes(fileExt)
}

/**
 * 检查文件大小
 * @param {Object} file - 文件对象
 * @param {number} maxFileSize - 最大文件大小（MB）
 * @returns {boolean} 是否通过验证
 */
export function checkFileSize(file, maxFileSize) {
    const maxBytes = maxFileSize * 1024 * 1024
    return file.size <= maxBytes
}

/**
 * 决定上传方式
 * @param {Object} file - 文件对象
 * @param {number} threshold - 分片阈值（字节）
 * @returns {string} 上传方式：'chunk' 或 'normal'
 */
export function determineUploadMethod(file, threshold = UPLOAD_CONFIG.CHUNK_THRESHOLD) {
    return file.size > threshold ? 'chunk' : 'normal'
}

/**
 * 创建文件验证函数
 * @param {Object} options - 验证选项
 * @returns {Object} 验证函数对象
 */
export function createFileValidator(options = {}) {
    const {
        allowedTypes = [],
        maxFileSize = 10,
        maxCount = 5,
        currentCount = () => 0,
        queueCount = () => 0
    } = options

    /**
     * 验证单个文件
     * @param {Object} file - 文件对象
     * @returns {Object} 验证结果
     */
    function validateFile(file) {
        const fileName = getFileName(file)
        
        // 检查数量限制
        const totalCount = currentCount() + queueCount()
        if (totalCount >= maxCount) {
            return { valid: false, error: `最多只能上传 ${maxCount} 个文件` }
        }

        // 检查文件类型
        if (!checkFileType(file, allowedTypes)) {
            return { valid: false, error: `文件 ${fileName} 类型不支持` }
        }

        // 检查文件大小
        if (!checkFileSize(file, maxFileSize)) {
            return { valid: false, error: `文件 ${fileName} 大小超过 ${maxFileSize}MB` }
        }

        return { valid: true }
    }

    /**
     * 验证多个文件
     * @param {Array} files - 文件数组
     * @param {Array} existingQueue - 已存在的队列
     * @returns {Object} 验证结果
     */
    function validateFiles(files, existingQueue = []) {
        const validFiles = []
        const errors = []

        for (const file of files) {
            const validation = validateFile(file)
            if (!validation.valid) {
                errors.push(validation.error)
                continue
            }

            // 检查重复文件
            const fileName = getFileName(file)
            const isDuplicate = existingQueue.some(queueFile => {
                const queueFileName = getFileName(queueFile)
                return queueFileName === fileName && queueFile.size === file.size
            })
            
            if (isDuplicate) {
                errors.push(`文件 ${fileName} 已在队列中`)
                continue
            }

            validFiles.push(file)
        }

        return { validFiles, errors }
    }

    return { validateFile, validateFiles }
}

// ================================
// 环境检测函数
// ================================

/**
 * 检测是否为H5环境
 * @returns {boolean} 是否为H5环境
 */
function isH5Environment() {
    return typeof window !== 'undefined' && window.navigator
}

/**
 * 检测是否为微信小程序环境
 * @returns {boolean} 是否为微信小程序环境
 */
function isMiniProgramEnvironment() {
    return typeof wx !== 'undefined' && wx.uploadFile
}

/**
 * 检测是否为预签名URL
 * @param {string} url - URL地址
 * @returns {boolean} 是否为预签名URL
 */
function isPresignedUrl(url) {
    return url.includes('X-Amz-Signature') || 
           url.includes('Signature=') ||
           url.includes('x-oss-signature')
}

/**
 * 检查存储配置是否支持直传
 * @param {Object} config - 存储配置
 * @returns {boolean} 是否支持直传
 */
function shouldUseDirectUpload(config) {
    if (!config) {
        return false
    }
    
    // 只根据配置中的 enableDirectUpload 字段来判断，不再依赖存储类型
    const directUploadEnabled = config.enableDirectUpload === 1 || config.enableDirectUpload === '1'
    
    return directUploadEnabled
}

// ================================
// 网络API函数
// ================================

/**
 * 加载存储配置
 * @returns {Promise<Array>} 存储配置列表
 */
export async function loadStorageConfigs() {
    try {
        return await getFileConfigs({ enabledOnly: true })
    } catch (error) {
        console.error('加载存储配置失败:', error)
        return []
    }
}

/**
 * 获取文件访问URL
 * @param {string} filePath - 文件路径
 * @param {number} configId - 配置ID
 * @returns {Promise<string>} 文件URL
 */
export async function getImageUrl(filePath, configId) {
    try {
        const params = { filePath }
        if (configId) {
            params.configId = configId
        }

        const response = await getFileUrl(params)
        const data = response.data || response
        return data.url || data.uploadUrl || filePath
    } catch (error) {
        console.warn('获取文件URL失败，使用原始路径:', error)
        return filePath
    }
}

// ================================
// 文件读取函数
// ================================

/**
 * 读取文件为ArrayBuffer（适配不同环境）
 * @param {File|string} filePathOrFile - 文件对象或文件路径
 * @returns {Promise<ArrayBuffer>} 文件内容
 */
function readFileAsArrayBuffer(filePathOrFile) {
    return new Promise((resolve, reject) => {
        const isH5 = isH5Environment()
        
        if (isH5 && filePathOrFile instanceof File) {
            // H5环境下直接读取File对象
            const reader = new FileReader()
            reader.onload = (e) => resolve(e.target.result)
            reader.onerror = () => reject(new Error('H5环境读取文件失败'))
            reader.readAsArrayBuffer(filePathOrFile)
        } else {
            // 原生APP环境，使用uni.getFileSystemManager()
            if (typeof uni.getFileSystemManager !== 'function') {
                reject(new Error('当前环境不支持文件系统管理器'))
                return
            }
            
            const fs = uni.getFileSystemManager()
            fs.readFile({
                filePath: filePathOrFile,
                success: (res) => resolve(res.data),
                fail: (error) => reject(new Error('读取文件失败: ' + error.errMsg))
            })
        }
    })
}

// ================================
// 核心上传函数
// ================================

/**
 * 后端上传（适配uni-app）
 * @param {Object} fileObj - 文件对象
 * @param {number} configId - 配置ID
 * @param {string} uploadDir - 上传目录
 * @param {Function} onProgress - 进度回调
 * @returns {Promise<void>}
 */
export async function uploadWithBackend(fileObj, configId, uploadDir, onProgress) {
    return new Promise((resolve, reject) => {
        const uploadTask = uni.uploadFile({
            url: config.baseUrl + '/file/upload',
            filePath: fileObj.file.path || fileObj.file.tempFilePath,
            name: 'file',
            formData: {
                configId: configId,
                ...(uploadDir && { dirPath: uploadDir }),
                // 传递原始文件名，确保压缩后的文件保持原名
                ...(fileObj.name && { fileName: fileObj.name })
            },
            header: createAuthHeaders(),
            success: (res) => {
                try {
                    const response = JSON.parse(res.data)
                    if (response.code === 200) {
                        const data = response.data || response
                        fileObj.filePath = data.filePath || data.url
                        fileObj.url = data.uploadUrl || data.url
                        fileObj.fileName = data.fileName || getFileName(fileObj)
                        fileObj.size = data.fileSize || data.size || fileObj.size
                        resolve()
                    } else {
                        reject(new Error(response.msg || '上传失败'))
                    }
                } catch (error) {
                    reject(new Error('解析响应失败: ' + error.message))
                }
            },
            fail: (error) => {
                reject(new Error('后端上传失败: ' + error.errMsg))
            }
        })

        // 监听上传进度
        if (onProgress) {
            uploadTask.onProgressUpdate((res) => {
                const progress = res.progress
                fileObj.progress = progress
                onProgress(progress)
            })
        }
    })
}

/**
 * H5环境下使用XMLHttpRequest上传
 * @param {Object} fileObj - 文件对象
 * @param {Object} tokenData - 令牌数据
 * @param {Function} onProgress - 进度回调
 * @returns {Promise<void>}
 */
function uploadWithXHR(fileObj, tokenData, onProgress) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest()

        xhr.upload.onprogress = (event) => {
            if (event.lengthComputable) {
                const progress = (event.loaded / event.total) * 100
                fileObj.progress = progress
                if (onProgress) onProgress(progress)
            }
        }

        xhr.onload = () => {
            if (xhr.status === 200 || xhr.status === 204) {
                fileObj.filePath = tokenData.filePath || tokenData.url
                fileObj.url = tokenData.url || tokenData.filePath
                fileObj.fileName = tokenData.fileName || getFileName(fileObj)
                resolve()
            } else {
                const errorMsg = xhr.responseText && xhr.responseText.includes('<Error>') 
                    ? parseMinIOError(xhr.responseText)
                    : `上传失败: ${xhr.status}`
                reject(new Error(errorMsg))
            }
        }

        xhr.onerror = () => {
            reject(new Error('网络错误'))
        }

        xhr.open('PUT', tokenData.uploadUrl)

        if (tokenData.headers) {
            Object.entries(tokenData.headers).forEach(([key, value]) => {
                xhr.setRequestHeader(key, value)
            })
        }

        // 如果有压缩后的 Blob，需要创建带正确文件名的 File 对象
        let fileToUpload = fileObj.file
        if (fileObj.file._blob && fileObj.name) {
            // 压缩后的文件，创建新的 File 对象保持原始文件名
            fileToUpload = new File([fileObj.file._blob], fileObj.name, {
                type: 'image/jpeg',
                lastModified: Date.now()
            })
        }

        xhr.send(fileToUpload)
    })
}

/**
 * 微信小程序环境下上传到阿里云OSS预签名URL
 * 使用uni.request的PUT方法发送二进制数据
 */
async function uploadToAliyunOSSWithWxRequest(fileObj, tokenData, onProgress) {
    return new Promise((resolve, reject) => {
        const filePath = fileObj.file.path || fileObj.file.tempFilePath
        
        // 读取文件为base64
        uni.getFileSystemManager().readFile({
            filePath: filePath,
            encoding: 'base64',
            success: (fileRes) => {
                // 将base64转换为ArrayBuffer
                const base64Data = fileRes.data
                const buffer = uni.base64ToArrayBuffer(base64Data)
                
                // 使用uni.request发送PUT请求
                uni.request({
                    url: tokenData.uploadUrl,
                    method: 'PUT',
                    data: buffer,
                    header: {
                        'Content-Type': fileObj.type || 'application/octet-stream'
                        // 不添加任何认证头，预签名URL已包含认证信息
                    },
                    success: (res) => {
                        if (res.statusCode === 200 || res.statusCode === 204) {
                            fileObj.filePath = tokenData.filePath || tokenData.url
                            fileObj.url = tokenData.url || tokenData.filePath
                            fileObj.fileName = tokenData.fileName || getFileName(fileObj)
                            resolve()
                        } else {
                            const errorMsg = res.data && typeof res.data === 'string' && res.data.includes('<Error>')
                                ? parseMinIOError(res.data)
                                : `上传失败: ${res.statusCode}`
                            reject(new Error(errorMsg))
                        }
                    },
                    fail: (error) => {
                        reject(new Error('阿里云OSS上传失败: ' + error.errMsg))
                    }
                })
                
                // 注意：uni.request不支持上传进度回调
                // 设置一个模拟进度
                if (onProgress) {
                    fileObj.progress = 50
                    onProgress(50)
                }
            },
            fail: (error) => {
                reject(new Error('读取文件失败: ' + error.errMsg))
            }
        })
    })
}

/**
 * 微信小程序环境下上传到腾讯云COS预签名URL
 * 使用uni.request的PUT方法发送二进制数据
 */
async function uploadToTencentCOSWithWxRequest(fileObj, tokenData, onProgress) {
    return new Promise((resolve, reject) => {
        const filePath = fileObj.file.path || fileObj.file.tempFilePath
        
        // 读取文件为base64
        uni.getFileSystemManager().readFile({
            filePath: filePath,
            encoding: 'base64',
            success: (fileRes) => {
                // 将base64转换为ArrayBuffer
                const base64Data = fileRes.data
                const buffer = uni.base64ToArrayBuffer(base64Data)
                
                // 使用uni.request发送PUT请求
                uni.request({
                    url: tokenData.uploadUrl,
                    method: 'PUT',
                    data: buffer,
                    header: {
                        'Content-Type': fileObj.type || 'application/octet-stream'
                        // 不添加任何认证头，预签名URL已包含认证信息
                    },
                    success: (res) => {
                        if (res.statusCode === 200 || res.statusCode === 204) {
                            fileObj.filePath = tokenData.filePath || tokenData.url
                            fileObj.url = tokenData.url || tokenData.filePath
                            fileObj.fileName = tokenData.fileName || getFileName(fileObj)
                            resolve()
                        } else {
                            const errorMsg = res.data && typeof res.data === 'string' && res.data.includes('<Error>')
                                ? parseMinIOError(res.data)
                                : `上传失败: ${res.statusCode}`
                            reject(new Error(errorMsg))
                        }
                    },
                    fail: (error) => {
                        reject(new Error('腾讯云COS上传失败: ' + error.errMsg))
                    }
                })
                
                // 注意：uni.request不支持上传进度回调
                // 设置一个模拟进度
                if (onProgress) {
                    fileObj.progress = 50
                    onProgress(50)
                }
            },
            fail: (error) => {
                reject(new Error('读取文件失败: ' + error.errMsg))
            }
        })
    })
}

/**
 * 微信小程序环境下上传到MinIO预签名URL
 * 使用uni.request的PUT方法发送二进制数据
 */
async function uploadToMinIOWithWxRequest(fileObj, tokenData, onProgress) {
    return new Promise((resolve, reject) => {
        const filePath = fileObj.file.path || fileObj.file.tempFilePath
        
        // 读取文件为base64
        uni.getFileSystemManager().readFile({
            filePath: filePath,
            encoding: 'base64',
            success: (fileRes) => {
                // 将base64转换为ArrayBuffer
                const base64Data = fileRes.data
                const buffer = uni.base64ToArrayBuffer(base64Data)
                
                // 使用uni.request发送PUT请求
                uni.request({
                    url: tokenData.uploadUrl,
                    method: 'PUT',
                    data: buffer,
                    header: {
                        'Content-Type': fileObj.type || 'application/octet-stream'
                        // 不添加任何认证头，预签名URL已包含认证信息
                    },
                    success: (res) => {
                        if (res.statusCode === 200 || res.statusCode === 204) {
                            fileObj.filePath = tokenData.filePath || tokenData.url
                            fileObj.url = tokenData.url || tokenData.filePath
                            fileObj.fileName = tokenData.fileName || getFileName(fileObj)
                            resolve()
                        } else {
                            const errorMsg = res.data && typeof res.data === 'string' && res.data.includes('<Error>')
                                ? parseMinIOError(res.data)
                                : `上传失败: ${res.statusCode}`
                            reject(new Error(errorMsg))
                        }
                    },
                    fail: (error) => {
                        reject(new Error('MinIO上传失败: ' + error.errMsg))
                    }
                })
                
                // 注意：uni.request不支持上传进度回调
                // 设置一个模拟进度
                if (onProgress) {
                    fileObj.progress = 50
                    onProgress(50)
                }
            },
            fail: (error) => {
                reject(new Error('读取文件失败: ' + error.errMsg))
            }
        })
    })
}

/**
 * 直传上传（适配uni-app）
 * @param {Object} fileObj - 文件对象
 * @param {number} configId - 配置ID
 * @param {string} uploadDir - 上传目录
 * @param {Function} onProgress - 进度回调
 * @returns {Promise<void>}
 */
export async function uploadWithDirectUpload(fileObj, configId, uploadDir, onProgress) {
    try {
        // 获取上传令牌
        const tokenResponse = await request({
            url: '/file/token',
            method: 'post',
            data: {
                fileName: getFileName(fileObj),
                configId: configId,
                dirPath: uploadDir || undefined,
                fileType: fileObj.type
            },
            header: {
                'repeatSubmit': 'false'
            }
        })

        if (tokenResponse.code !== 200) {
            throw new Error(tokenResponse.msg || '获取上传令牌失败')
        }

        const tokenData = tokenResponse.data

        // 检查运行环境并使用对应的上传方式
        const isH5 = isH5Environment()
        
        // 如果是H5环境且支持XMLHttpRequest，使用原生上传方式
        if (isH5 && typeof XMLHttpRequest !== 'undefined') {
            return uploadWithXHR(fileObj, tokenData, onProgress)
        }
        
        // 检查是否为预签名URL
        const isPresigned = isPresignedUrl(tokenData.uploadUrl)
        
        // 对于微信小程序环境的预签名URL，根据存储类型使用专用方法
        const isMiniProgram = isMiniProgramEnvironment()
        
        if (isMiniProgram && isPresigned) {
            // 通过后端API获取存储配置来判断存储类型
            try {
                const { getFileConfigById } = await import('@/store/modules/fileConfig')
                const config = await getFileConfigById(configId)
                
                if (config && config.storageType) {
                    switch (config.storageType) {
                        case 'minio':
            return uploadToMinIOWithWxRequest(fileObj, tokenData, onProgress)
                        case 'aliyun':
                            return uploadToAliyunOSSWithWxRequest(fileObj, tokenData, onProgress)
                        case 'tencent':
                            return uploadToTencentCOSWithWxRequest(fileObj, tokenData, onProgress)
                        default:
                            break
                    }
                }
            } catch (error) {
                console.warn('获取存储配置失败，使用默认上传方式:', error)
            }
        }
        
        // 原生APP环境，使用uni.uploadFile
        const filePath = fileObj.file.path || fileObj.file.tempFilePath

        return new Promise((resolve, reject) => {
            const uploadConfig = {
                url: tokenData.uploadUrl,
                filePath: filePath,
                name: 'file', // 大部分云存储使用'file'字段
            success: (res) => {
                if (res.statusCode === 200 || res.statusCode === 204) {
                    fileObj.filePath = tokenData.filePath || tokenData.url
                    fileObj.url = tokenData.url || tokenData.filePath
                    fileObj.fileName = tokenData.fileName || getFileName(fileObj)
                    resolve()
                } else {
                        const errorMsg = res.data && typeof res.data === 'string' && res.data.includes('<Error>')
                            ? parseMinIOError(res.data)
                            : `上传失败: ${res.statusCode}`
                    reject(new Error(errorMsg))
                }
            },
            fail: (error) => {
                reject(new Error('直传上传失败: ' + error.errMsg))
            }
            }

            // 重要：使用预签名URL时，不要添加额外的认证头
            // 预签名URL本身已经包含了所有必要的认证信息
            if (!isPresigned && tokenData.headers && Object.keys(tokenData.headers).length > 0) {
                uploadConfig.header = tokenData.headers
            }

            // 添加表单数据（如果有且不是预签名URL）
            if (!isPresigned && tokenData.formData && Object.keys(tokenData.formData).length > 0) {
                uploadConfig.formData = tokenData.formData
            }

            // 对于某些云存储（如阿里云OSS），可能需要特殊的字段名
            if (tokenData.fieldName) {
                uploadConfig.name = tokenData.fieldName
            }

            const uploadTask = uni.uploadFile(uploadConfig)

            // 监听上传进度
            if (onProgress) {
                uploadTask.onProgressUpdate((res) => {
                    const progress = res.progress
                    fileObj.progress = progress
                    onProgress(progress)
                })
            }
        })
    } catch (error) {
        throw new Error('直传上传失败: ' + error.message)
    }
}

/**
 * 普通上传（自动选择直传或后端）
 * @param {Object} fileObj - 文件对象
 * @param {Object} config - 存储配置
 * @param {number} configId - 配置ID
 * @param {string} uploadDir - 上传目录
 * @param {Function} onProgress - 进度回调
 * @returns {Promise<void>}
 */
export async function uploadFileNormally(fileObj, config, configId, uploadDir, onProgress) {
        // 验证配置对象
        if (!config) {
            console.error('❌ 存储配置不能为空')
            throw new Error('存储配置不能为空')
        }
        
        if (!config.storageType) {
            console.error('❌ 存储配置缺少storageType:', config)
            throw new Error('存储配置缺少存储类型信息')
        }
        
    // 检查是否应该使用直传
    const useDirectUpload = shouldUseDirectUpload(config)
    fileObj.supportDirectUpload = useDirectUpload

    if (useDirectUpload) {
        await uploadWithDirectUpload(fileObj, configId, uploadDir, onProgress)
        } else {
        await uploadWithBackend(fileObj, configId, uploadDir, onProgress)
    }
}

// ================================
// 分片上传函数
// ================================

/**
 * H5环境下直接上传分片（带重试机制）
 * @param {ArrayBuffer} chunk - 分片数据
 * @param {string} uploadUrl - 上传URL
 * @param {number} partNumber - 分片序号
 * @param {number} maxRetries - 最大重试次数
 * @returns {Promise<Object>} 分片上传结果
 */
export async function uploadChunkDirectH5(chunk, uploadUrl, partNumber, maxRetries = UPLOAD_CONFIG.MAX_RETRIES) {
    let lastError = null
    
    // 重试机制：失败后重试指定次数
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            return await new Promise((resolve, reject) => {
                const xhr = new XMLHttpRequest()

                // 设置超时时间
                xhr.timeout = UPLOAD_CONFIG.TIMEOUT.CHUNK_UPLOAD

                xhr.onload = () => {
                    if (xhr.status === 200 || xhr.status === 204) {
                        // 从响应头中获取ETag
                        const etag = xhr.getResponseHeader('ETag') || 
                                   xhr.getResponseHeader('etag') || 
                                   `etag-${partNumber}-${Date.now()}`
                        resolve({
                            partNumber: partNumber, 
                            etag: etag.replace(/"/g, '') // 移除引号
                        })
                    } else {
                        reject(new Error(`HTTP ${xhr.status}: ${xhr.statusText}`))
                    }
                }

                xhr.onerror = () => {
                    reject(new Error('网络连接错误'))
                }

                xhr.ontimeout = () => {
                    reject(new Error('请求超时'))
                }

                xhr.open('PUT', uploadUrl)
                xhr.send(chunk)
            })
            
        } catch (error) {
            lastError = error
            
            // 如果不是最后一次重试，等待一段时间再重试
            if (attempt < maxRetries) {
                const delayMs = Math.min(
                    UPLOAD_CONFIG.RETRY_DELAY.BASE * Math.pow(UPLOAD_CONFIG.RETRY_DELAY.MULTIPLIER, attempt - 1), 
                    UPLOAD_CONFIG.RETRY_DELAY.MAX
                )
                await new Promise(resolve => setTimeout(resolve, delayMs))
            } else {
                console.error(`分片 ${partNumber} H5直传失败:`, error.message)
            }
        }
    }
    
    // 所有重试都失败了
    throw new Error(`分片 ${partNumber} H5直传失败（已重试 ${maxRetries} 次）: ${lastError?.message || '未知错误'}`)
}

/**
 * H5环境下上传分片到后端（带重试机制）
 * @param {ArrayBuffer} chunk - 分片数据
 * @param {string} uploadId - 上传ID
 * @param {number} partNumber - 分片序号
 * @param {number} maxRetries - 最大重试次数
 * @returns {Promise<Object>} 分片上传结果
 */
export async function uploadChunkToBackendH5(chunk, uploadId, partNumber, maxRetries = UPLOAD_CONFIG.MAX_RETRIES) {
    const formData = new FormData()
    formData.append('file', new Blob([chunk]))
    formData.append('uploadId', uploadId)
    formData.append('partNumber', partNumber)

    let lastError = null
    
    // 重试机制：失败后重试指定次数
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            const response = await fetch(config.baseUrl + '/file/chunk/upload', {
                method: 'POST',
                body: formData,
                headers: createAuthHeaders()
            })

            const result = await response.json()

            if (result.code === 200) {
                return {
                    partNumber: partNumber,
                    etag: result.data?.etag || result.etag || `etag-${partNumber}`
                }
            } else {
                throw new Error(result.msg || '上传分片失败')
            }
        } catch (error) {
            lastError = error
            
            // 如果不是最后一次重试，等待一段时间再重试
            if (attempt < maxRetries) {
                const delayMs = Math.min(
                    UPLOAD_CONFIG.RETRY_DELAY.BASE * Math.pow(UPLOAD_CONFIG.RETRY_DELAY.MULTIPLIER, attempt - 1), 
                    UPLOAD_CONFIG.RETRY_DELAY.MAX
                )
                await new Promise(resolve => setTimeout(resolve, delayMs))
            } else {
                console.error(`分片 ${partNumber} H5后端上传失败:`, error.message)
            }
        }
    }
    
    // 所有重试都失败了
    throw new Error(`分片 ${partNumber} H5后端上传失败（已重试 ${maxRetries} 次）: ${lastError?.message || '未知错误'}`)
}

/**
 * 直接上传分片（uni-app版本，带重试机制）
 * @param {ArrayBuffer} chunk - 分片数据
 * @param {string} uploadUrl - 上传URL
 * @param {number} partNumber - 分片序号
 * @param {number} maxRetries - 最大重试次数
 * @returns {Promise<Object>} 分片上传结果
 */
export async function uploadChunkDirectUni(chunk, uploadUrl, partNumber, maxRetries = UPLOAD_CONFIG.MAX_RETRIES) {
    let lastError = null
    
    // 重试机制：失败后重试指定次数
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            return await new Promise((resolve, reject) => {
                // 在uni-app中，我们需要先将ArrayBuffer写入临时文件
                const fs = uni.getFileSystemManager()
                // 微信小程序环境下获取用户数据路径
                const userDataPath = uni.env.USER_DATA_PATH || 'wxfile://user'
                const tempFilePath = `${userDataPath}/temp_chunk_${partNumber}_${Date.now()}_${attempt}.tmp`

                fs.writeFile({
                    filePath: tempFilePath, 
                    data: chunk, 
                    success: () => {
                        // 使用uni.uploadFile上传分片到预签名URL
                        const uploadConfig = {
                            url: uploadUrl, 
                            filePath: tempFilePath, 
                            name: 'file', // 大多数云存储使用'file'字段
                            success: (res) => {
                                // 清理临时文件
                                fs.unlink({ filePath: tempFilePath, fail: () => {} })

                                if (res.statusCode === 200 || res.statusCode === 204) {
                                    // 从响应头中获取ETag
                                    const etag = res.header.ETag || 
                                               res.header.etag || 
                                               res.header['etag'] || 
                                               `etag-${partNumber}-${Date.now()}`
                                    resolve({
                                        partNumber: partNumber, 
                                        etag: etag.replace(/"/g, '') // 移除引号
                                    })
                                } else {
                                    reject(new Error(`HTTP ${res.statusCode}: ${res.errMsg || '未知错误'}`))
                                }
                            }, 
                            fail: (error) => {
                                // 清理临时文件
                                fs.unlink({ filePath: tempFilePath, fail: () => {} })
                                reject(new Error(`网络连接错误: ${error.errMsg}`))
                            }
                        }

                        // 重要：对于预签名URL，不要添加任何额外的认证头
                        // 预签名URL本身已经包含了认证信息
                        uni.uploadFile(uploadConfig)
                    }, 
                    fail: (error) => {
                        reject(new Error(`写入临时文件失败: ${error.errMsg}`))
                    }
                })
            })
            
        } catch (error) {
            lastError = error
            
            // 如果不是最后一次重试，等待一段时间再重试
            if (attempt < maxRetries) {
                const delayMs = Math.min(
                    UPLOAD_CONFIG.RETRY_DELAY.BASE * Math.pow(UPLOAD_CONFIG.RETRY_DELAY.MULTIPLIER, attempt - 1), 
                    UPLOAD_CONFIG.RETRY_DELAY.MAX
                )
                await new Promise(resolve => setTimeout(resolve, delayMs))
            } else {
                console.error(`分片 ${partNumber} uni-app直传失败:`, error.message)
            }
        }
    }
    
    // 所有重试都失败了
    throw new Error(`分片 ${partNumber} uni-app直传失败（已重试 ${maxRetries} 次）: ${lastError?.message || '未知错误'}`)
}

/**
 * 微信小程序环境下上传分片到阿里云OSS预签名URL
 */
async function uploadChunkToAliyunOSSWithWxRequest(chunk, uploadUrl, partNumber, maxRetries = UPLOAD_CONFIG.MAX_RETRIES) {
    let lastError = null
    
    // 重试机制：失败后重试指定次数
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            return await new Promise((resolve, reject) => {
                // 在微信小程序中，我们需要先将ArrayBuffer写入临时文件
                const fs = uni.getFileSystemManager()
                // 微信小程序环境下获取用户数据路径
                const userDataPath = uni.env.USER_DATA_PATH || 'wxfile://user'
                const tempFilePath = `${userDataPath}/temp_oss_chunk_${partNumber}_${Date.now()}_${attempt}.tmp`

                fs.writeFile({
                    filePath: tempFilePath,
                    data: chunk,
                    success: () => {
                        // 读取文件为base64然后转为ArrayBuffer用于PUT请求
                        fs.readFile({
                            filePath: tempFilePath,
                            encoding: 'base64',
                            success: (fileRes) => {
                                // 将base64转换为ArrayBuffer
                                const base64Data = fileRes.data
                                const buffer = uni.base64ToArrayBuffer(base64Data)
                                
                                // 使用uni.request发送PUT请求（阿里云OSS需要PUT方法）
                                uni.request({
                                    url: uploadUrl,
                                    method: 'PUT',
                                    data: buffer,
                                    timeout: UPLOAD_CONFIG.TIMEOUT.CHUNK_UPLOAD,
                                    header: {
                                        'Content-Type': 'application/octet-stream'
                                        // 不添加任何认证头，预签名URL已包含认证信息
                                    },
                                    success: (res) => {
                                        // 清理临时文件
                                        fs.unlink({ filePath: tempFilePath, fail: () => {} })
                                        
                                        if (res.statusCode === 200 || res.statusCode === 204) {
                                            // 从响应头中获取ETag
                                            const etag = res.header.ETag || 
                                                       res.header.etag || 
                                                       res.header['etag'] || 
                                                       `etag-${partNumber}-${Date.now()}`
                                            resolve({
                                                partNumber: partNumber,
                                                etag: etag.replace(/"/g, '') // 移除引号
                                            })
                                        } else {
                                            reject(new Error(`HTTP ${res.statusCode}: ${res.errMsg || '未知错误'}`))
                                        }
                                    },
                                    fail: (error) => {
                                        // 清理临时文件
                                        fs.unlink({ filePath: tempFilePath, fail: () => {} })
                                        reject(new Error(`网络连接错误: ${error.errMsg}`))
                                    }
                                })
                            },
                            fail: (error) => {
                                // 清理临时文件
                                fs.unlink({ filePath: tempFilePath, fail: () => {} })
                                reject(new Error(`读取临时文件失败: ${error.errMsg}`))
                            }
                        })
                    },
                    fail: (error) => {
                        reject(new Error(`写入临时文件失败: ${error.errMsg}`))
                    }
                })
            })
            
        } catch (error) {
            lastError = error
            
            // 如果不是最后一次重试，等待一段时间再重试
            if (attempt < maxRetries) {
                const delayMs = Math.min(
                    UPLOAD_CONFIG.RETRY_DELAY.BASE * Math.pow(UPLOAD_CONFIG.RETRY_DELAY.MULTIPLIER, attempt - 1), 
                    UPLOAD_CONFIG.RETRY_DELAY.MAX
                )
                await new Promise(resolve => setTimeout(resolve, delayMs))
            } else {
                console.error(`分片 ${partNumber} 微信小程序阿里云OSS上传失败:`, error.message)
            }
        }
    }
    
    // 所有重试都失败了
    throw new Error(`分片 ${partNumber} 微信小程序阿里云OSS上传失败（已重试 ${maxRetries} 次）: ${lastError?.message || '未知错误'}`)
}

/**
 * 微信小程序环境下上传分片到腾讯云COS预签名URL
 */
async function uploadChunkToTencentCOSWithWxRequest(chunk, uploadUrl, partNumber, maxRetries = UPLOAD_CONFIG.MAX_RETRIES) {
    let lastError = null
    
    // 重试机制：失败后重试指定次数
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            return await new Promise((resolve, reject) => {
                // 在微信小程序中，我们需要先将ArrayBuffer写入临时文件
                const fs = uni.getFileSystemManager()
                // 微信小程序环境下获取用户数据路径
                const userDataPath = uni.env.USER_DATA_PATH || 'wxfile://user'
                const tempFilePath = `${userDataPath}/temp_cos_chunk_${partNumber}_${Date.now()}_${attempt}.tmp`

                fs.writeFile({
                    filePath: tempFilePath,
                    data: chunk,
                    success: () => {
                        // 读取文件为base64然后转为ArrayBuffer用于PUT请求
                        fs.readFile({
                            filePath: tempFilePath,
                            encoding: 'base64',
                            success: (fileRes) => {
                                // 将base64转换为ArrayBuffer
                                const base64Data = fileRes.data
                                const buffer = uni.base64ToArrayBuffer(base64Data)
                                
                                // 使用uni.request发送PUT请求（腾讯云COS需要PUT方法）
                                uni.request({
                                    url: uploadUrl,
                                    method: 'PUT',
                                    data: buffer,
                                    timeout: UPLOAD_CONFIG.TIMEOUT.CHUNK_UPLOAD,
                                    header: {
                                        'Content-Type': 'application/octet-stream'
                                        // 不添加任何认证头，预签名URL已包含认证信息
                                    },
                                    success: (res) => {
                                        // 清理临时文件
                                        fs.unlink({ filePath: tempFilePath, fail: () => {} })
                                        
                                        if (res.statusCode === 200 || res.statusCode === 204) {
                                            // 从响应头中获取ETag
                                            const etag = res.header.ETag || 
                                                       res.header.etag || 
                                                       res.header['etag'] || 
                                                       `etag-${partNumber}-${Date.now()}`
                                            resolve({
                                                partNumber: partNumber,
                                                etag: etag.replace(/"/g, '') // 移除引号
                                            })
                                        } else {
                                            reject(new Error(`HTTP ${res.statusCode}: ${res.errMsg || '未知错误'}`))
                                        }
                                    },
                                    fail: (error) => {
                                        // 清理临时文件
                                        fs.unlink({ filePath: tempFilePath, fail: () => {} })
                                        reject(new Error(`网络连接错误: ${error.errMsg}`))
                                    }
                                })
                            },
                            fail: (error) => {
                                // 清理临时文件
                                fs.unlink({ filePath: tempFilePath, fail: () => {} })
                                reject(new Error(`读取临时文件失败: ${error.errMsg}`))
                            }
                        })
                    },
                    fail: (error) => {
                        reject(new Error(`写入临时文件失败: ${error.errMsg}`))
                    }
                })
            })
            
        } catch (error) {
            lastError = error
            
            // 如果不是最后一次重试，等待一段时间再重试
            if (attempt < maxRetries) {
                const delayMs = Math.min(
                    UPLOAD_CONFIG.RETRY_DELAY.BASE * Math.pow(UPLOAD_CONFIG.RETRY_DELAY.MULTIPLIER, attempt - 1), 
                    UPLOAD_CONFIG.RETRY_DELAY.MAX
                )
                await new Promise(resolve => setTimeout(resolve, delayMs))
            } else {
                console.error(`分片 ${partNumber} 微信小程序腾讯云COS上传失败:`, error.message)
            }
        }
    }
    
    // 所有重试都失败了
    throw new Error(`分片 ${partNumber} 微信小程序腾讯云COS上传失败（已重试 ${maxRetries} 次）: ${lastError?.message || '未知错误'}`)
}

/**
 * 微信小程序环境下上传分片到MinIO预签名URL
 */
async function uploadChunkToMinIOWithWxRequest(chunk, uploadUrl, partNumber, maxRetries = UPLOAD_CONFIG.MAX_RETRIES) {
    let lastError = null
    
    // 重试机制：失败后重试指定次数
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            return await new Promise((resolve, reject) => {
                // 在微信小程序中，我们需要先将ArrayBuffer写入临时文件
                const fs = uni.getFileSystemManager()
                // 微信小程序环境下获取用户数据路径
                const userDataPath = uni.env.USER_DATA_PATH || 'wxfile://user'
                const tempFilePath = `${userDataPath}/temp_chunk_${partNumber}_${Date.now()}_${attempt}.tmp`

                fs.writeFile({
                    filePath: tempFilePath,
                    data: chunk,
                    success: () => {
                        // 读取文件为base64然后转为ArrayBuffer用于PUT请求
                        fs.readFile({
                            filePath: tempFilePath,
                            encoding: 'base64',
                            success: (fileRes) => {
                                // 将base64转换为ArrayBuffer
                                const base64Data = fileRes.data
                                const buffer = uni.base64ToArrayBuffer(base64Data)
                                
                                // 使用uni.request发送PUT请求
                                uni.request({
                                    url: uploadUrl,
                                    method: 'PUT',
                                    data: buffer,
                                    timeout: UPLOAD_CONFIG.TIMEOUT.CHUNK_UPLOAD,
                                    header: {
                                        'Content-Type': 'application/octet-stream'
                                        // 不添加任何认证头，预签名URL已包含认证信息
                                    },
                                    success: (res) => {
                                        // 清理临时文件
                                        fs.unlink({ filePath: tempFilePath, fail: () => {} })
                                        
                                        if (res.statusCode === 200 || res.statusCode === 204) {
                                            // 从响应头中获取ETag
                                            const etag = res.header.ETag || 
                                                       res.header.etag || 
                                                       res.header['etag'] || 
                                                       `etag-${partNumber}-${Date.now()}`
                                            resolve({
                                                partNumber: partNumber,
                                                etag: etag.replace(/"/g, '') // 移除引号
                                            })
                                        } else {
                                            reject(new Error(`HTTP ${res.statusCode}: ${res.errMsg || '未知错误'}`))
                                        }
                                    },
                                    fail: (error) => {
                                        // 清理临时文件
                                        fs.unlink({ filePath: tempFilePath, fail: () => {} })
                                        reject(new Error(`网络连接错误: ${error.errMsg}`))
                                    }
                                })
                            },
                            fail: (error) => {
                                // 清理临时文件
                                fs.unlink({ filePath: tempFilePath, fail: () => {} })
                                reject(new Error(`读取临时文件失败: ${error.errMsg}`))
                            }
                        })
                    },
                    fail: (error) => {
                        reject(new Error(`写入临时文件失败: ${error.errMsg}`))
                    }
                })
            })
            
        } catch (error) {
            lastError = error
            
            // 如果不是最后一次重试，等待一段时间再重试
            if (attempt < maxRetries) {
                const delayMs = Math.min(
                    UPLOAD_CONFIG.RETRY_DELAY.BASE * Math.pow(UPLOAD_CONFIG.RETRY_DELAY.MULTIPLIER, attempt - 1), 
                    UPLOAD_CONFIG.RETRY_DELAY.MAX
                )
                await new Promise(resolve => setTimeout(resolve, delayMs))
            } else {
                console.error(`分片 ${partNumber} 微信小程序MinIO上传失败:`, error.message)
            }
        }
    }
    
    // 所有重试都失败了
    throw new Error(`分片 ${partNumber} 微信小程序MinIO上传失败（已重试 ${maxRetries} 次）: ${lastError?.message || '未知错误'}`)
}

/**
 * 上传分片到后端（uni-app版本，带重试机制）
 * @param {ArrayBuffer} chunk - 分片数据
 * @param {string} uploadId - 上传ID
 * @param {number} partNumber - 分片序号
 * @param {number} maxRetries - 最大重试次数
 * @returns {Promise<Object>} 分片上传结果
 */
export async function uploadChunkToBackendUni(chunk, uploadId, partNumber, maxRetries = UPLOAD_CONFIG.MAX_RETRIES) {
    let lastError = null
    
    // 重试机制：失败后重试指定次数
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            return await new Promise((resolve, reject) => {
                // 在uni-app中，我们需要先将ArrayBuffer写入临时文件
                const fs = uni.getFileSystemManager()
                // 微信小程序环境下获取用户数据路径
                const userDataPath = uni.env.USER_DATA_PATH || 'wxfile://user'
                const tempFilePath = `${userDataPath}/temp_chunk_${partNumber}_${Date.now()}_${attempt}.tmp`

                fs.writeFile({
                    filePath: tempFilePath,
                    data: chunk,
                    success: () => {
                        // 使用uni.uploadFile上传分片
                        uni.uploadFile({
                            url: config.baseUrl + '/file/chunk/upload',
                            filePath: tempFilePath,
                            name: 'file',
                            formData: {
                                uploadId: uploadId,
                                partNumber: partNumber
                            },
                            header: createAuthHeaders(),
                            success: (res) => {
                                // 清理临时文件
                                fs.unlink({ filePath: tempFilePath, fail: () => {} })

                                try {
                                    const response = JSON.parse(res.data)
                                    if (response.code === 200) {
                                        resolve({
                                            partNumber: partNumber,
                                            etag: response.data?.etag || response.etag || `etag-${partNumber}`
                                        })
                                    } else {
                                        reject(new Error(response.msg || '上传分片失败'))
                                    }
                                } catch (error) {
                                    reject(new Error(`解析响应失败: ${error.message}`))
                                }
                            },
                            fail: (error) => {
                                // 清理临时文件
                                fs.unlink({ filePath: tempFilePath, fail: () => {} })
                                reject(new Error(`网络连接错误: ${error.errMsg}`))
                            }
                        })
                    },
                    fail: (error) => {
                        reject(new Error(`写入临时文件失败: ${error.errMsg}`))
                    }
                })
            })
            
        } catch (error) {
            lastError = error
            
            // 如果不是最后一次重试，等待一段时间再重试
            if (attempt < maxRetries) {
                const delayMs = Math.min(
                    UPLOAD_CONFIG.RETRY_DELAY.BASE * Math.pow(UPLOAD_CONFIG.RETRY_DELAY.MULTIPLIER, attempt - 1), 
                    UPLOAD_CONFIG.RETRY_DELAY.MAX
                )
                await new Promise(resolve => setTimeout(resolve, delayMs))
            } else {
                console.error(`分片 ${partNumber} uni-app后端上传失败:`, error.message)
            }
        }
    }
    
    // 所有重试都失败了
    throw new Error(`分片 ${partNumber} uni-app后端上传失败（已重试 ${maxRetries} 次）: ${lastError?.message || '未知错误'}`)
}

/**
 * 分片上传（适配uni-app）
 * @param {Object} fileObj - 文件对象
 * @param {Object} config - 存储配置
 * @param {number} configId - 配置ID
 * @param {string} uploadDir - 上传目录
 * @param {number} chunkSize - 分片大小（MB）
 * @param {Function} onProgress - 进度回调
 * @returns {Promise<void>}
 */
export async function uploadFileWithChunks(fileObj, config, configId, uploadDir, chunkSize, onProgress) {
    const chunkSizeBytes = chunkSize * 1024 * 1024
    const totalChunks = Math.ceil(fileObj.file.size / chunkSizeBytes)

    try {
        // 1. 初始化分片上传
        const initResponse = await initMultipartUpload({
            fileName: getFileName(fileObj),
            fileSize: fileObj.file.size,
            configId: configId,
            dirPath: uploadDir || undefined,
            fileType: fileObj.type
        })

        if (initResponse.code !== 200) {
            throw new Error(initResponse.msg || '初始化分片上传失败')
        }

        fileObj.uploadId = initResponse.data.uploadId
        fileObj.filePath = initResponse.data.filePath

        // 2. 批量获取预签名URL（如果支持）
        let presignedUrls = new Map()
        
    // 验证配置对象
    if (!config) {
        console.error('❌ 存储配置不能为空')
        throw new Error('存储配置不能为空')
    }
    
    if (!config.storageType) {
        console.error('❌ 存储配置缺少storageType:', config)
        throw new Error('存储配置缺少存储类型信息')
    }
    
        // 检查是否应该使用直传
        const useDirectUpload = shouldUseDirectUpload(config)
        fileObj.supportDirectUpload = useDirectUpload

        if (useDirectUpload) {
            const urlResponse = await getBatchPresignedUploadUrls({
                uploadId: fileObj.uploadId,
                totalParts: totalChunks,
                configId: configId,
                filePath: fileObj.filePath
            })

            if (urlResponse.code === 200 && urlResponse.data.urls) {
                urlResponse.data.urls.forEach(urlInfo => {
                    presignedUrls.set(urlInfo.partNumber, urlInfo.uploadUrl)
                })
            }
        }

        // 3. 上传分片（带重试和错误处理）
        const etags = []
        const concurrency = UPLOAD_CONFIG.CONCURRENCY
        let hasErrors = false
        let uploadCancelled = false

        // 添加取消上传的标志
        fileObj.cancelUpload = () => {
            uploadCancelled = true
        }

        // 检查环境并读取文件
        const isH5 = isH5Environment()
        let fileContent
        
        if (isH5 && fileObj.file instanceof File) {
            // H5环境下直接使用File对象
            fileContent = await readFileAsArrayBuffer(fileObj.file)
        } else {
            // 原生APP环境，使用文件路径
            const filePath = fileObj.file.path || fileObj.file.tempFilePath
            fileContent = await readFileAsArrayBuffer(filePath)
        }

        console.log(`开始分片上传: 文件大小=${fileObj.file.size}字节, 总分片数=${totalChunks}`)

        try {
            // 分批上传分片
            for (let i = 0; i < totalChunks; i += concurrency) {
                // 检查是否被取消
                if (uploadCancelled) {
                    throw new Error('用户取消了上传')
                }

                const batch = []
                const batchPromises = []

                for (let j = 0; j < concurrency && i + j < totalChunks; j++) {
                    const chunkIndex = i + j
                    const start = chunkIndex * chunkSizeBytes
                    const end = Math.min(start + chunkSizeBytes, fileContent.byteLength)
                    const chunk = fileContent.slice(start, end)
                    const partNumber = chunkIndex + 1

                    let uploadPromise
                    if (useDirectUpload && presignedUrls.has(partNumber)) {
                        const uploadUrl = presignedUrls.get(partNumber)
                        // 检查环境选择合适的分片上传方法
                        if (isH5) {
                            uploadPromise = uploadChunkDirectH5(chunk, uploadUrl, partNumber)
                        } else {
                            // 检查是否为微信小程序环境，根据存储类型选择上传方法
                            const isMiniProgram = isMiniProgramEnvironment()
                            
                            if (isMiniProgram && config.storageType) {
                                switch (config.storageType) {
                                    case 'minio':
                                uploadPromise = uploadChunkToMinIOWithWxRequest(chunk, uploadUrl, partNumber)
                                        break
                                    case 'aliyun':
                                        uploadPromise = uploadChunkToAliyunOSSWithWxRequest(chunk, uploadUrl, partNumber)
                                        break
                                    case 'tencent':
                                        uploadPromise = uploadChunkToTencentCOSWithWxRequest(chunk, uploadUrl, partNumber)
                                        break
                                    default:
                                uploadPromise = uploadChunkDirectUni(chunk, uploadUrl, partNumber)
                                        break
                                }
                            } else {
                                uploadPromise = uploadChunkDirectUni(chunk, uploadUrl, partNumber)
                            }
                        }
                    } else {
                        if (isH5) {
                            uploadPromise = uploadChunkToBackendH5(chunk, fileObj.uploadId, partNumber)
                        } else {
                            uploadPromise = uploadChunkToBackendUni(chunk, fileObj.uploadId, partNumber)
                        }
                    }

                    batchPromises.push(uploadPromise)
                    batch.push({ partNumber, chunkIndex })
                }

                // 等待当前批次完成，如果有失败的分片，记录错误但继续其他分片
                const results = await Promise.allSettled(batchPromises)
                
                for (let k = 0; k < results.length; k++) {
                    const result = results[k]
                    const { partNumber } = batch[k]
                    
                    if (result.status === 'fulfilled') {
                        etags.push(result.value)
                    } else {
                        hasErrors = true
                        console.error(`分片 ${partNumber} 上传失败:`, result.reason.message)
                        // 如果是关键错误（如认证失败），立即停止
                        if (result.reason.message.includes('认证') || result.reason.message.includes('权限') || 
                            result.reason.message.includes('401') || result.reason.message.includes('403')) {
                            throw new Error(`分片上传失败: ${result.reason.message}`)
                        }
                    }
                }

                // 更新进度 - 基于已完成的批次
                const completedBatches = Math.floor(i / concurrency) + 1
                const totalBatches = Math.ceil(totalChunks / concurrency)
                const progress = Math.min((completedBatches / totalBatches) * 100, 99) // 不要显示100%，等完成后再显示
                fileObj.progress = progress
                if (onProgress) onProgress(progress)

                // 如果有错误且错误率过高，停止上传
                const completedChunks = i + batch.length
                const errorRate = (completedChunks - etags.length) / completedChunks
                if (hasErrors && errorRate > UPLOAD_CONFIG.ERROR_RATE_THRESHOLD) {
                    throw new Error(`分片上传错误率过高 (${Math.round(errorRate * 100)}%)，已停止上传`)
                }
            }

            // 检查是否所有分片都成功上传
            if (etags.length !== totalChunks) {
                const missingParts = []
                for (let i = 1; i <= totalChunks; i++) {
                    if (!etags.find(e => e.partNumber === i)) {
                        missingParts.push(i)
                    }
                }
                throw new Error(`有 ${totalChunks - etags.length} 个分片上传失败，缺失分片: ${missingParts.join(', ')}`)
            }

        } catch (uploadError) {
            console.error('分片上传过程中出现错误:', uploadError.message)
            throw uploadError
        }

        // 4. 完成分片上传
        try {
            const completeResponse = await completeMultipartUpload({
                filePath: fileObj.filePath,
                uploadId: fileObj.uploadId,
                parts: etags.sort((a, b) => a.partNumber - b.partNumber),
                configId: configId
            })

            if (completeResponse.code !== 200) {
                throw new Error(completeResponse.msg || '完成分片上传失败')
            }
            
            // 上传成功，设置进度为100%
            fileObj.progress = 100
            if (onProgress) onProgress(100)
            
        } catch (completeError) {
            console.error('完成分片上传失败:', completeError.message)
            throw new Error('完成分片上传失败: ' + completeError.message)
        }
        
    } catch (error) {
        // 发生错误时自动取消分片上传
        if (fileObj.uploadId && !uploadCancelled) {
            try {
                await abortMultipartUpload(fileObj.filePath, fileObj.uploadId, configId)
            } catch (abortError) {
                console.error('取消分片上传失败:', abortError.message)
            }
        }
        
        // 清理取消函数
        delete fileObj.cancelUpload
        throw new Error('分片上传失败: ' + error.message)
    } finally {
        // 清理取消函数
        delete fileObj.cancelUpload
    }
}

// ================================
// 上传管理器
// ================================

/**
 * 创建上传管理器
 * @param {Object} options - 配置选项
 * @returns {Object} 上传管理器对象
 */
export function createUploadManager(options = {}) {
    const {
        allowedTypes = [],
        maxFileSize = 1024, // 默认1GB
        maxCount = 5,
        chunkSize = 5,
        autoUpload = true
    } = options

    // 创建文件验证器
    const validator = createFileValidator({
        allowedTypes,
        maxFileSize,
        maxCount,
        currentCount: () => options.currentCount?.() || 0,
        queueCount: () => options.queueCount?.() || 0
    })

    return {
        // 验证器
        validator,
        
        // 工具函数
        generateFileId,
        formatFileSize,
        checkFileType,
        checkFileSize,
        determineUploadMethod,
        copyPath,
        generatePreviewUrl,
        
        // 网络函数
        loadStorageConfigs,
        getImageUrl,
        
        // 上传函数
        uploadWithBackend,
        uploadWithDirectUpload,
        uploadFileWithChunks,
        uploadFileNormally,
        
        // 分片上传函数
        uploadChunkDirectUni,
        uploadChunkToBackendUni,
        
        // 其他函数
        abortMultipartUpload
    }
}

// ================================
// 导出
// ================================

export default {
    // 工具函数
    generateFileId,
    formatFileSize,
    copyPath,
    generatePreviewUrl,
    
    // 验证函数
    checkFileType,
    checkFileSize,
    determineUploadMethod,
    createFileValidator,
    
    // 网络函数
    loadStorageConfigs,
    getImageUrl,
    
    // 上传函数
    uploadWithBackend,
    uploadWithDirectUpload,
    uploadFileWithChunks,
    uploadFileNormally,
    
    // 分片上传函数
    uploadChunkDirectH5,
    uploadChunkToBackendH5,
    uploadChunkDirectUni,
    uploadChunkToBackendUni,
    
    // 管理器
    createUploadManager
}